﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using TicTacToe.Core.Statistics;

namespace TicTacToe.UI.Statistics
{
    partial class StatisticsWindow : Window
    {
        #region general statistics helper methods

        string _getLongestGame(Database db)
        {
            try
            {
                return (from g in db.GetGameWinners().Select(n => n.Key)
                        let ts = db.GetTurnsForGame(g)
                        where ts.Any()
                        let lt = ts.OrderByDescending(n => n.Index).First()
                        select lt.Time - g.StartTime)
                       .OrderByDescending(n => n.TotalMilliseconds)
                       .First().TotalSeconds.Round().ToString() + " Seconds";
            }
            catch
            {
                return "N/A";
            }
        }

        string _getLongestGame_Turns(Database db)
        {
            try
            {
                return (from g in db.GetGameWinners().Select(n => n.Key)
                        select db.GetTurnsForGame(g).Count())
                        .Max().ToString();
            }
            catch
            {
                return "N/A";
            }
        }

        string _getAvgGameTime(Database db)
        {
            try
            {
                return (from w in db.GetGameWinners()
                        let ts = db.GetTurnsForGame(w.Key)
                        let lt = ts.OrderByDescending(n => n.Index).First()
                        select lt.Time - w.Key.StartTime)
                        .Average(n => n.Seconds).Round().ToString() + " Seconds";
            }
            catch
            {
                return "N/A";
            }
        }

        string _getTotalGameTime(Database db)
        {
            try
            {
                return (from g in db.GetGames()
                        let ts = db.GetTurnsForGame(g)
                        where ts.Any()
                        let lt = ts.OrderByDescending(n => n.Index).First()
                        select lt.Time - g.StartTime)
                        .Sum(n => n.TotalMinutes).Round().ToString() + " Minutes";
            }
            catch
            {
                return "N/A";
            }
        }

        string _getAvgTurnsPerGame(Database db)
        {
            try
            {
                return (from g in db.GetGames()
                        let ts = db.GetTurnsForGame(g)
                        where ts.Any()
                        select ts.Count())
                        .Average().Round().ToString();
            }
            catch
            {
                return "N/A";
            }
        }

        string _getShortestGameTime(Database db)
        {
            try
            {
                return (from g in db.GetGameWinners().Select(n=>n.Key)
                        let ts = db.GetTurnsForGame(g)
                        where ts.Any()
                        let lt = ts.OrderByDescending(n => n.Index).First()
                        select lt.Time - g.StartTime)
                       .OrderBy(n => n.TotalMilliseconds)
                       .First().TotalSeconds.Round().ToString() + " Seconds";
            }
            catch
            {
                return "N/A";
            }
        }

        string _getShortestGame_Turns(Database db)
        {
            try
            {
                return (from g in db.GetGameWinners().Select(n => n.Key)
                        select db.GetTurnsForGame(g).Count())
                        .Min().ToString();
            }
            catch
            {
                return "N/A";
            }
        }

        string _getPlayerWithMostTurns(Database db)
        {
            try
            {
                var first = (from p in db.GetPlayers()
                             select db.GetTurns().Where(n => n.Player == p))
                            .OrderByDescending(n => n.Count())
                            .First();

                return db.GetPlayerById(first.First().Player).Name + " (" + first.Count() + ")";
            }
            catch
            {
                return "N/A";
            }
        }

        string _getPlayerWithMostGames(Database db)
        {
            try
            {
                var first = db.GetPlayers()
                            .OrderByDescending(n => db.GetGames().Where(m => m.Player1 == n || m.Player2 == n).Count())
                            .First();
                return first.Name + " (" + db.GetGames().Where(m => m.Player1 == first || m.Player2 == first).Count() + ")";
            }
            catch
            {
                return "N/A";
            }
        }

        #endregion

        #region player-specific statistics helper methods

        int _getGamesPlayed(Database db, PlayerID pid)
        {
            return db.GetGames().Where(n => n.Player1 == pid || n.Player2 == pid).Count();
        }

        int _getGamesFinished(Database db, PlayerID pid)
        {
            return db.GetGameWinners().Select(n => n.Key).Where(n => n.Player1 == pid || n.Player2 == pid).Count();
        }

        int _getGamesWon(Database db, PlayerID pid)
        {
            return db.GetGameWinners().Where(n => n.Value == pid).Count();
        }

        int _getPlayerTurns(Database db, PlayerID pid)
        {
            return db.GetTurns().Where(n => n.Player == pid).Count();
        }

        double _getAvgTurnsPerGame(Database db, PlayerID pid)
        {
            return db.GetGames().Where(n => n.Player1 == pid || n.Player2 == pid)
                   .Select(n => db.GetTurnsForGame(n).Count()).Average().Round();
        }

        string _getAvgGameTime(Database db, PlayerID pid)
        {
            try
            {
                return (from g in db.GetGames().Where(n => n.Player1 == pid || n.Player2 == pid)
                        let ts = db.GetTurnsForGame(g)
                        let lt = ts.OrderByDescending(n => n.Index).First()
                        select lt.Time - g.StartTime)
                        .Average(n => n.Seconds).Round().ToString() + " Seconds";
            }
            catch
            {
                return "N/A";
            }
        }

        string _getLongestGame(Database db, PlayerID pid)
        {
            try
            {
                return (from g in db.GetGameWinners().Select(n => n.Key)
                        where g.Player1 == pid || g.Player2 == pid
                        let ts = db.GetTurnsForGame(g)
                        where ts.Any()
                        let lt = ts.OrderByDescending(n => n.Index).First()
                        select lt.Time - g.StartTime)
                       .OrderByDescending(n => n.TotalMilliseconds)
                       .First().TotalSeconds.Round().ToString() + " Seconds";
            }
            catch
            {
                return "N/A";
            }
        }

        string _getLongestGame_Turns(Database db, PlayerID pid)
        {
            try
            {
                return (from g in db.GetGameWinners().Select(n => n.Key)
                        where g.Player1 == pid || g.Player2 == pid
                        select db.GetTurnsForGame(g).Count())
                        .Max().ToString();
            }
            catch
            {
                return "N/A";
            }
        }

        string _getShortestGameTime(Database db, PlayerID pid)
        {
            try
            {
                return (from g in db.GetGameWinners().Select(n => n.Key)
                        where g.Player1 == pid || g.Player2 == pid
                        let ts = db.GetTurnsForGame(g)
                        where ts.Any()
                        let lt = ts.OrderByDescending(n => n.Index).First()
                        select lt.Time - g.StartTime)
                       .OrderBy(n => n.TotalMilliseconds)
                       .First().TotalSeconds.Round().ToString() + " Seconds";
            }
            catch
            {
                return "N/A";
            }
        }

        string _getShortestGame_Turns(Database db, PlayerID pid)
        {
            try
            {
                return (from g in db.GetGameWinners().Select(n => n.Key)
                        where g.Player1 == pid || g.Player2 == pid
                        select db.GetTurnsForGame(g).Count())
                        .Min().ToString();
            }
            catch
            {
                return "N/A";
            }
        }

        string _getTotalGameTime(Database db, PlayerID pid)
        {
            try
            {
                return (from g in db.GetGames()
                        where g.Player1 == pid || g.Player2 == pid
                        let ts = db.GetTurnsForGame(g)
                        where ts.Any()
                        let lt = ts.OrderByDescending(n => n.Index).First()
                        select lt.Time - g.StartTime)
                        .Sum(n => n.TotalMinutes).Round().ToString() + " Minutes";
            }
            catch
            {
                return "N/A";
            }
        }

        #endregion

        void LoadGeneralStatistics()
        {
            Task.Factory.StartNew<IEnumerable<StatisticsItem>>(() =>
            {
                var inst = DatabaseManager.Instance;
                Func<string, object, StatisticsItem> createItem = (label, content) =>
                    new StatisticsItem(label, content);

                return new Dictionary<string, object>
                {
                    {"Games Played", inst.GetGames().Count()},
                    {"Games Finished", inst.GetGameWinners().Count()},
                    {"Different Player Names", inst.GetPlayers().Count()},
                    {"Average Game Duration", _getAvgGameTime(inst)},
                    {"Longest Finished Game", _getLongestGame(inst)},
                    {"Longest Finished Game (Turns)", _getLongestGame_Turns(inst)},
                    {"Shortest Finished Game", _getShortestGameTime(inst)},
                    {"Shortest Finished Game (Turns)", _getShortestGame_Turns(inst)},
                    {"Total Game Time", _getTotalGameTime(inst)},
                    {"Total Turn Count", inst.GetTurns().Count()},
                    {"Average Turns per Game", _getAvgTurnsPerGame(inst)},
                    {"Player With Most Games", _getPlayerWithMostGames(inst)},
                    {"Player With Most Turns",_getPlayerWithMostTurns(inst)},
                }.Select(n => Dispatcher.Invoke(createItem, n.Key, n.Value) as StatisticsItem);
            }).ContinueWith(task =>
            {
                Action<StatisticsItem> a = item => generalStats.Children.Add(item);

                foreach (var i in task.Result)
                    Dispatcher.Invoke(a, i);
            });
        }

        void LoadPlayerSpecificStatistics(string name)
        {
            specificStats.Children.Clear();

            Task.Factory.StartNew<IEnumerable<StatisticsItem>>(() =>
            {
                var inst = DatabaseManager.Instance;
                var pid = inst.GetPlayerByName(name);
                Func<string, object, StatisticsItem> createItem = (label, content) =>
                    new StatisticsItem(label, content);

                return new Dictionary<string, object>
                {
                    {"Games Played", _getGamesPlayed(inst, pid)},
                    {"Games Finished", _getGamesFinished(inst, pid)},
                    {"Games Won", _getGamesWon(inst, pid)},
                    {"Games Lost", _getGamesFinished(inst, pid)-_getGamesWon(inst, pid)},
                    {"Total Game Time", _getTotalGameTime(inst, pid)},
                    {"Total Turn Count", _getPlayerTurns(inst, pid)},
                    {"Average Turns Per Game", _getAvgTurnsPerGame(inst, pid)},
                    {"Average Game Duration", _getAvgGameTime(inst, pid)},
                    {"Longest Finished Game", _getLongestGame(inst, pid)},
                    {"Longest Finished Game (Turns)", _getLongestGame_Turns(inst, pid)},
                    {"Shortest Finished Game", _getShortestGameTime(inst, pid)},
                    {"Shortest Finished Game (Turns)", _getShortestGame_Turns(inst, pid)},
                }.Select(n => Dispatcher.Invoke(createItem, n.Key, n.Value) as StatisticsItem);
            }).ContinueWith(task =>
            {
                Action<StatisticsItem> a = item => specificStats.Children.Add(item);

                foreach (var i in task.Result)
                    Dispatcher.Invoke(a, i);
            });
        }

        public StatisticsWindow()
        {
            InitializeComponent();

            LoadGeneralStatistics();
            nameComboBox.DataContext = DatabaseManager.Instance.GetPlayers().Select(n => n.Name).OrderBy(n => n);
            dbSizeLabel.Content = ((double)DatabaseManager.Instance.DatabaseSize / 1024D).Round() + " KB";
            checkBox1.IsChecked = DatabaseManager.EnableDataCollection;
        }

        private void nameComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            var item = nameComboBox.SelectedItem;
            if (item != null)
                LoadPlayerSpecificStatistics(item as string);
        }

        private void wipeDbButton_Click(object sender, RoutedEventArgs e)
        {
            DatabaseManager.Instance.Wipe();
        }

        private void checkBox1_Unchecked(object sender, RoutedEventArgs e)
        {
            DatabaseManager.EnableDataCollection = checkBox1.IsChecked.Value;
        }
        
        private void checkBox1_Checked(object sender, RoutedEventArgs e)
        {
            DatabaseManager.EnableDataCollection = checkBox1.IsChecked.Value;
        }
    }
}
